package team.archai.nexus.boot.auth.support;

import cn.hutool.json.JSONUtil;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.interfaces.DecodedJWT;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.util.Assert;
import team.archai.nexus.boot.auth.entity.AuthToken;
import team.archai.nexus.boot.auth.entity.JwtParam;
import team.archai.nexus.boot.auth.exception.ExpiredException;
import team.archai.nexus.boot.auth.exception.InvalidException;
import team.archai.nexus.boot.auth.model.AuthenticatedInfo;
import team.archai.nexus.boot.auth.token.IAuth;
import team.archai.nexus.boot.auth.token.JwtAuth;
import team.archai.nexus.boot.cache.entity.CacheKV;
import team.archai.nexus.boot.cache.support.CacheHelper;
import team.archai.nexus.boot.config.AuthProperties;
import team.archai.nexus.boot.tool.AESUtil;
import team.archai.nexus.boot.tool.DesUtil;
import team.archai.nexus.boot.tool.Tool;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.Date;


/**
 * @description: jwt token 工具类
 * @author: lishibin
 * @create: 2020-07-11 01:04
 **/
public abstract class JwtTokenHelper implements IJwtHelper {

    private static final Log log = LogFactory.getLog(JwtTokenHelper.class);

    @Resource
    protected CacheHelper cacheHelper;

    protected static CacheHelper cacheUtil;

    protected static AuthProperties authProp;

    @Resource
    protected AuthProperties authProperties;

    @PostConstruct
    public void init() {
        JwtTokenHelper.cacheUtil = cacheHelper;
        JwtTokenHelper.authProp = authProperties;
    }

    protected static String createToken(AuthToken authToken) {
        String subject = authToken.getSubject();
        String signKey = authToken.getSignKey();
        Assert.hasText(subject, "create toke subject is must not null.");
        Assert.hasText(signKey, "create toke key is must not null.");
        String issuer = Tool.uuid();
        log.info("create jwt token. params =>" + authToken.toString());
        String random = authToken.getInitialRandom();
        long now = System.currentTimeMillis();
        String additionalJson = JSONUtil.toJsonStr(authToken.getAdditional());
        JWTCreator.Builder builder = JWT.create()
                //签发人
                .withIssuer(issuer)
                //分配id
                .withJWTId(Tool.uuid())
                //签发给用户的用户名
                .withSubject(subject)
                //对前面key 进行des 加密 key 是用户的主体
                .withIssuedAt(new Date(now)).withClaim(issuer, DesUtil.encrypt(AESUtil.encode(random, subject), signKey))
                .withKeyId(random)
                //对扩展信息 des 加密 key 是随机数
                .withClaim(subject, DesUtil.encrypt(random, additionalJson))
                .withClaim("credential", DesUtil.encrypt(random, Tool.toStr(authToken.getCredential())));
        if (authToken.getExp() > 0) {
            long expiration = now + authToken.getExp();
            builder.withExpiresAt(new Date(expiration));
        }
        String token = builder.sign(Algorithm.HMAC256(signKey));
        log.info("created token. =>" + token);
        return token;
    }


    /**
     * @program: JwtHelper
     * @description: 生成key
     * @author: lishibin
     * @create: 6:08 PM
     **/
    private static String generalKey(Object id) {
        return AESUtil.encode(Tool.uuid(), Tool.toStr(id));
    }


    /**
     * @program: JwtHelper
     * @description: 创建jwt token
     * @author: lishibin
     * @create: 7:12 PM
     **/
    protected static String createJwt(JwtParam jwtParam) {
        Assert.notNull(jwtParam, "createJwt by jwtParam bean,jwtParam is must not null.");
        log.info("createJwt by jwtParam bean jwtParam is =>" + jwtParam);
        String key = generalKey(jwtParam.getCredential());
        String random = jwtParam.getInitialRandom();
        String subject = jwtParam.getSubject();
        String encrypt = DesUtil.encrypt(subject + random, key);
        jwtParam.setSignKey(encrypt);
        if (authProp.getEnableCache()) {
            CacheKV kv = CacheKV.builder().K(Tool.toStr(jwtParam.getCredential())).V(encrypt).build();
            cacheUtil.put(kv);
        }
        long exp = jwtParam.getExp();
        if (0 == exp) {
            exp = authProp.getTimeout() * 1000;
        }
        AuthToken authToken = AuthToken.builder()
                .additional(jwtParam.getAdditional())
                .initialRandom(random)
                .exp(jwtParam.getExp())
                .subject(jwtParam.getSubject())
                .signKey(encrypt)
                .credential(jwtParam.getCredential())
                .build();
        String token = createToken(authToken);
        log.info("createJwt by jwtParam bean .expired is " + exp + "ms");
        return token;
    }


    /**
     * @program: JwtHelper
     * @description: 创建jwt token
     * @author: lishibin
     * @create: 7:12 PM
     **/
    @Override
    public String create(JwtParam jwtParam) {
        Assert.notNull(jwtParam, "create jwt token,jwtParam is must not null.");
        log.info("create jwt token by jwtParam bean. jwtParam =>" + jwtParam.toString());
        String token = createJwt(jwtParam);
        log.info("created token by jwtClaims bean. =>" + token);
        return token;
    }


    /**
     * @program: JwtTokenHelper
     * @description: 验证token
     * @author: lishibin
     * @create: 12:36 AM
     **/
    public static DecodedJWT validate(String token, String key) throws InvalidException {
        JWTVerifier verifier = JWT.require(Algorithm.HMAC256(key)).build();
        try {
            return verifier.verify(token);
        } catch (JWTVerificationException e) {
            log.error(e);
            throw new InvalidException("Token invalid.");
        }
    }


    /**
     * @program: JwtHelper
     * @description: 解析jwt token
     * @author: lishibin
     * @create: 11:15 PM
     **/
    @Override
    public IAuth parse(String token) throws InvalidException, ExpiredException {
        Assert.hasText(token, "parse jwt token is must not null.");
        Assert.hasText(token, "parse jwt sign is must not null.");
        String fmt = "token:[%s]";
        log.info("parse jwt token. params =>" + String.format(fmt, token));
        JwtAuth jwtAuth = JwtAuth.builder().build();
        AuthenticatedInfo principal = AuthenticatedInfo.builder().build();
        try {
            DecodedJWT originToken = JWT.decode(token);
            //解密signKey 用于jwt 验证
            String keyId = originToken.getKeyId();
            String encodeSignKey = originToken.getClaim(originToken.getIssuer()).asString();
            originToken = validate(token, DesUtil.decrypt(AESUtil.encode(keyId, originToken.getSubject()), encodeSignKey));
            principal.initByJWT(originToken, principal);
            principal.setToken(token);
        } catch (JWTDecodeException e) {
            log.error("parse jwt token error, token expired case by :", e);
            throw new InvalidException("Token Invalid");
        }
        jwtAuth.setToken(token);
        jwtAuth.setAuthenticatedInfo(principal);
        log.info("parsed jwt token. jwtAuth =>" + jwtAuth);
        return jwtAuth;
    }


    /**
     * @program: JwtHelper
     * @description: 撤销jwt token
     * @author: lishibin
     * @create: 7:12 PM
     **/
    @Override
    public void revoke(JwtParam jwtParam) {
        Assert.notNull(jwtParam, "revokeJwt by jwtParam bean,jwtParam is must not null.");
        log.info("revoke jwt token. jwtParam =>" + jwtParam.toString());
        CacheKV kv = CacheKV.builder().K(Tool.toStr(jwtParam.getCredential())).build();
        cacheUtil.remove(kv);
        log.info("revoked token. =>");
    }
}
